﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Clandestine;
using Clandestine.Entities;
using System.Xml;
using System.Reflection;
using System.Drawing;

namespace Slick.MapEditor
{
    public class EntityType
    {
        public string FullName;
        public string FriendlyName;
        public bool Trigger;

        public override string ToString()
        {
            return FriendlyName;
        }
    }

    public delegate void EntityAdded(Entity e);
    public delegate void EntityListRefreshed();
    public delegate void EntitySelected(Entity e);

    public class EntityManager : System.Collections.IEnumerable
    {
        public static EntityType[] ListOfAvailableEntities; // List of all entities available to us. [thats rly an array!?]

        public Map Map;
        public Entity SelectedEntity; // Selected entity instance on map
        public EntityType SelectedEntityType; // Selected entity in list of entities we want to add!
        public object SelectedEntityBlob = new object(); // we need these blobs for lock statements as we can't lock(null)
        public object SelectedEntityTypeBlob = new object(); // and the above can be null!
        
        public event EntityAdded OnEntityAdded;
        public event EntitySelected OnEntitySelected;
        public event EntityListRefreshed OnEntityListRefreshed;

        private List<Entity> entities = new List<Entity>(); // Current instances of entities on the current map.
        private Dictionary<Trigger, Sprite> triggerSprites = new Dictionary<Trigger, Sprite>(); // Sprites for [otherwise invisible] trigger sprites.

        public Entity this[int index]
        {
            get 
            {
                return entities[index];
            }
        }

        public int Count
        {
            get
            {
                return entities.Count;
            }
        }

        public System.Collections.IEnumerator GetEnumerator()
        {
            return entities.GetEnumerator();
        }

        // Static constructor to get list of available entities.
        static EntityManager()
        {
            List<EntityType> validEntityTypes = new List<EntityType>();

            // Woo, reflection!
            // (stolen from mikey & adapted a little, eheh.)
            // PS. now stolen from blanky & adapted further 
            Type[] types = Assembly.GetAssembly(typeof(Clandestine.Entities.Entity)).GetTypes();
            foreach (Type t in types)
            {
                if (t.FullName == "Clandestine.MapPlayer")
                    continue; // mapplayer isn't an entity. ...but it might be!
                if (t.IsAbstract)
                    continue;

                if (t.IsSubclassOf(typeof(Clandestine.Entities.Entity)))
                {
                    EntityType et = new EntityType();
                    et.FullName = t.FullName;

                    et.Trigger = t.IsSubclassOf(typeof(Clandestine.Entities.Trigger));

                    if (et.FullName.StartsWith("Clandestine.Entities."))
                        et.FriendlyName = et.FullName.Substring("Clandestine.Entities.".Length);
                    else
                        et.FriendlyName = et.FullName;

                    validEntityTypes.Add(et);
                }
            }

            ListOfAvailableEntities = validEntityTypes.ToArray();
        }

        public void Clear()
        {
            lock (entities)
                foreach (Entity ent in entities)
                    ent.Dispose();
            entities = new List<Entity>();
        }

        // Constructor
        public EntityManager(Map map)
        {
            this.Map = map;
        }

        /// <summary>
        /// Gets the sprite for the specified trigger.
        /// </summary>
        /// <param name="t">The trigger we want the sprite for.</param>
        /// <returns>The sprite of the specified trigger.</returns>
        public Sprite GetTriggerSprite(Trigger t)
        {
            return triggerSprites[t];
        }

        /// <summary>
        /// Adds an entity to the current map.
        /// </summary>
        /// <param name="e">The entity to be added.</param>
        public void Add(Entity e)
        {
            lock (entities)
            {
                // Add to list of entities
                entities.Add(e);

                // Is it a trigger? Might need to make a sprite!
                if (e.GetType().IsSubclassOf(typeof(Clandestine.Entities.Trigger)))
                {
                    Sprite rep = new Sprite(Texture.GetTexture("white.png"));
                    rep.ScaleX = e.Size.Width;
                    rep.ScaleY = e.Size.Height;
                    rep.Position = e.Position;
                    rep.Layer = e.Layer + 0.001f;
                    rep.Color = ((Trigger)e).EditorColor;
                    lock (triggerSprites)
                        triggerSprites.Add((Trigger)e, rep);
                }

                // Events!
                if (OnEntityAdded != null)
                    OnEntityAdded(e);
            }
        }

        public bool IsTrigger(Entity e)
        {
            return e.GetType().IsSubclassOf(typeof(Clandestine.Entities.Trigger));
        }

        /// <summary>
        /// Used to change the selection of the current entity!
        /// </summary>
        /// <param name="e">The entity to be selected.</param>
        public void SelectEntity(Entity e)
        {
            lock (SelectedEntityBlob)
            {
                // Stop the highlighting of the previously selected entity
                if ((SelectedEntity != null) && (SelectedEntity.GetType().IsSubclassOf(typeof(Clandestine.Entities.Trigger))))
                {
                    // If it was a trigger sprite, this means changing the appropriate sprite.
                    lock (triggerSprites)
                        if (triggerSprites.ContainsKey((Trigger)SelectedEntity))
                            triggerSprites[(Trigger)SelectedEntity].IgnoreGlobalColor = false;
                }
                else if ((SelectedEntity != null) && (SelectedEntity.Sprite != null))
                    SelectedEntity.Sprite.IgnoreGlobalColor = false;

                // Make selected entity the new one & raise event! :)
                SelectedEntity = e;
                if (OnEntitySelected != null)
                    OnEntitySelected(e);

                // Highlight the newly selected entity
                if ((SelectedEntity != null) && (SelectedEntity.GetType().IsSubclassOf(typeof(Clandestine.Entities.Trigger))))
                    lock (triggerSprites)
                        triggerSprites[((Trigger)SelectedEntity)].IgnoreGlobalColor = true;
                else if (SelectedEntity != null)
                    SelectedEntity.Sprite.IgnoreGlobalColor = true;
            }
        }

        /// <summary>
        /// Deletes the selected entity
        /// </summary>
        internal void DeleteSelectedEntity()
        {
            if (SelectedEntity != null)
            {
                lock (entities)
                    lock (SelectedEntityBlob)
                        if (SelectedEntity != null)
                        {
                            entities.Remove(SelectedEntity);
                            SelectedEntity.Dispose();
                            SelectedEntity = null;
                        }
                if (OnEntityListRefreshed != null)
                    OnEntityListRefreshed();
            }
        }

        public void LoadEntities(XmlTextReader xml)
        {
            // Go through till </entities>
            while (!((xml.NodeType == XmlNodeType.EndElement) && (xml.Name == "entities")))
            {
                // New entity? -- Should be...
                if ((xml.NodeType == XmlNodeType.Element) && (xml.Name == "entity"))
                {
                    Entity e = (Entity)System.Activator.CreateInstance(xml.GetAttribute("assembly"), xml.GetAttribute("type")).Unwrap();

                    // Go through and add properties
                    while (!((xml.NodeType == XmlNodeType.EndElement) && (xml.Name == "entity")))
                    {
                        if (xml.NodeType == XmlNodeType.Element)
                        {
                            // Size
                            if (xml.Name == "size")
                                if (e.SupportsResizing)
                                    e.Size = new Size(int.Parse(xml.GetAttribute("width")), int.Parse(xml.GetAttribute("height")));
                                else
                                    Log.e("Map file tries to set .Size of an Entity ({0}) that does not support resizing!", e.GetType().FullName);

                            // Position
                            if (xml.Name == "position")
                                e.Position = new Point(int.Parse(xml.GetAttribute("x")), int.Parse(xml.GetAttribute("y")));

                            // Generic property
                            if (xml.Name == "property")
                            {
                                string name = xml.GetAttribute("name");
                                string strValue = xml.GetAttribute("value");
                                object value = null;
                                if (e.GetType().GetProperty(name).PropertyType == typeof(Rectangle))
                                {
                                    //haxy rectangle loading! :) 
                                    //A string that contains the position, width, and height of this Rectangle structure,for example, {X=a, Y=b, Width=c, Height=d} 
                                    string[] sc = strValue.Split(new string[] { " ; ", ",", "}", "{" }, StringSplitOptions.RemoveEmptyEntries);
                                    int a = int.Parse(sc[0].Substring(2, sc[0].Length - 2));
                                    int b = int.Parse(sc[1].Substring(2, sc[1].Length - 2));
                                    int c = int.Parse(sc[2].Substring(6, sc[2].Length - 6));
                                    int d = int.Parse(sc[3].Substring(7, sc[3].Length - 7));
                                    value = new Rectangle(a, b, c, d);
                                }
                                else if (e.GetType().GetProperty(name).PropertyType.IsEnum)
                                    value = Enum.Parse(e.GetType().GetProperty(name).PropertyType, strValue);
                                else
                                    value = Convert.ChangeType(strValue, e.GetType().GetProperty(name).PropertyType);
                                e.GetType().GetProperty(name).SetValue(e, value, null);
                            }
                        }
                        xml.Read();
                    }

                    Add(e);
                }

                xml.Read();
            }
        }

        public void SaveEntities(XmlTextWriter xml, int minx, int miny)
        {
            xml.WriteStartElement("entities");

            // Go through each entity
            lock (entities)
            {
                for (int i = 0; i < entities.Count; i++)
                {
                    xml.WriteStartElement("entity");
                    xml.WriteAttributeString("type", entities[i].GetType().FullName);
                    xml.WriteAttributeString("assembly", entities[i].GetType().Assembly.FullName);

                    // Get all the properties, and save the writable ones
                    PropertyInfo[] properties = entities[i].GetType().GetProperties();
                    foreach (PropertyInfo p in properties)
                    {
                        // Only save the write-able ones
                        if (p.CanWrite)
                        {
                            // Watch out for special cases
                            switch (p.Name)
                            {
                                // Stupid struct >.>
                                case "Position":
                                    Point pos = (Point)p.GetValue(entities[i], null);
                                    xml.WriteStartElement("position");
                                    xml.WriteAttributeString("x", (pos.X - minx).ToString());
                                    xml.WriteAttributeString("y", (pos.Y - miny).ToString());
                                    xml.WriteEndElement();
                                    break;

                                // Only save if .SupportsResizing
                                case "Size":
                                    if (entities[i].SupportsResizing)
                                    {
                                        Size size = (Size)p.GetValue(entities[i], null);
                                        xml.WriteStartElement("size");
                                        xml.WriteAttributeString("width", size.Width.ToString());
                                        xml.WriteAttributeString("height", size.Height.ToString());
                                        xml.WriteEndElement();
                                    }
                                    break;

                                // Generic saving
                                default:
                                    xml.WriteStartElement("property");
                                    xml.WriteAttributeString("name", p.Name);
                                    xml.WriteAttributeString("value", p.GetValue(entities[i], null).ToString());
                                    xml.WriteEndElement();
                                    break;
                            }
                        }
                    }

                    xml.WriteEndElement();
                }
            }

            xml.WriteEndElement();
        }
    }
}
